#include <cstdlib>
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <queue>
#include "server_thread_cliente.h"
#include "server_thread_enviar.h"
#include "server_thread_aceptar.h"
#include "server_thread_crear_partida.h"
#include "server_socket.h"
#include "server_Partida.h"
#include "server_Bomberman.h"
#include "server_Jugador.h"
#include "server_Evento.h"
#include "server_Partida.h"
#include "server_Campania.h"
#include "server_ColaDeEventos.h"
#include "common_Mutex.h"
#define PORT 4321
#define LARGO 5

using std::cout;
using std::endl;
using std::string;
using std::vector;
using std::stoi;
using std::queue;
using std::to_string;
using std::unordered_map;

Server_thread_cliente::Server_thread_cliente(Server_socket* s, Server_socket* nuevo) {
    socket = s;
    nuevo_socket = nuevo;
    agregadoAPartida = false;
    creadorPartida = false;
}

Server_thread_cliente::~Server_thread_cliente() {
}

/* Ejecutar: funcion donde hay un bucle para enviar y recibir informacion del cliente. 
 * Al principio, antes de empezar el juego, solo espera consultas del cliente (como partidas
 * disponibles, etc) y las responde. Luego se crea el jugador y se lo agrega a la lista de jugadores.
 * Finalmente se lanza un hilo para enviar cambios de estado y se usa este hilo para recibir eventos
 * del juego. */
void Server_thread_cliente::ejecutar() {
    log.clienteConectado(this->numeroCliente);
    bool correCliente = true;
    /**** BUCLE ANTES DE EMPEZAR EL JUEGO. ****/
    while (*correServer && correCliente && agregadoAPartida == false) {
        string buffer;
        string bufferEnviar;
        /**** RECIBIR ****/
        if (leerBuffer(buffer) == false) { //sacar luego
            correCliente = false;
            log.clienteDesconectado(this->numeroCliente);
            continue;
        }
        buffer.erase(0, 1); //le saco el pipe.           
        if (buffer[0] == 'A') {
            bufferEnviar = infoCampanias;
        } else if (buffer[0] == 'C') {
            crearPartidaEnCampania(buffer);
            bufferEnviar = agregarLargo(this->jugador->obtenerPersonaje()->serializar());
        } else if (buffer[0] == 'M') {
            bufferEnviar = agregarLargo(generarInfoPartidas());
        } else if (buffer[0] == 'U') {
            setNombreJugadorYAgregarAPartida(buffer);
            bufferEnviar = agregarLargo(this->jugador->obtenerPersonaje()->serializar());
        } else if (buffer[0] == 'S') {
            correCliente = false;
            bufferEnviar = "00003|OK";
        }
        /**** ENVIAR ****/
        int tam = bufferEnviar.size();
        if (socket->enviar(nuevo_socket->get_fd(), bufferEnviar, tam) == -1) {
            cout << "server_thread_cliente enviar" << endl;
        }
    }
    if (!correCliente) {
        nuevo_socket->cerrar();
        return;
    }
    /*************** SALA DE ESPERA ****************/
    bool empezar = false;
    while (empezar == false && correCliente) {
        string buffer;
        string bufferEnviar;
        if (leerBuffer(buffer) == false) {
            if (this->creadorPartida) {
                this->partida->cancelar();
            }
            correCliente = false;
            log.clienteDesconectado(this->numeroCliente);
        }
        buffer.erase(0, 1);
        this->partida->setFlagIniciar();
        if (buffer[0] == 'P') {
            if (this->partida->estaListaParaComenzar()) {
                if (this->creadorPartida || this->partida->estaJugando()) {
                    bufferEnviar = agregarLargo(cantJugadoresEnPartida().append("|SI"));
                } else {
                    bufferEnviar = agregarLargo(cantJugadoresEnPartida().append("|NO"));
                }
            } else {
                bufferEnviar = agregarLargo(cantJugadoresEnPartida().append("|NO"));
            }
        } else if (buffer[0] == 'I') {
            bufferEnviar = agregarLargo(this->partida->getTileMapInicial());
            this->partida->setFlagEmpezar();
            empezar = true;
        } else {
            continue;
        }
        int tam = bufferEnviar.size();
        if (socket->enviar(nuevo_socket->get_fd(), bufferEnviar, tam) == -1) {
            cout << "server_thread_cliente enviar" << endl;
        }
    }
    if (!correCliente) {
        nuevo_socket->cerrar();
        return;
    }
    /* hilo para enviar datos.*/
    Server_thread_enviar thread_send = Server_thread_enviar(socket, nuevo_socket);
    thread_send.setFlagsCorrer(correServer, &correCliente, &agregadoAPartida);
    thread_send.setTiempoActualizacion(tiempoActualizacion);
    thread_send.setPartida(this->partida);
    thread_send.iniciar();

    /**** BUCLE PARA RECIBIR EVENTOS DEL JUEGO****/
    while (*correServer && correCliente) {
        string buffer;
        if (leerBuffer(buffer) == false) {
            if (this->creadorPartida) {
                this->partida->cancelar();
            }
            log.clienteDesconectado(this->numeroCliente);
            correCliente = false;
            continue;
        }
        buffer.erase(0, 1); //le saco el pipe.
        Evento* evento = new Evento(buffer, this->jugador);
        this->partida->getColaDeEventos()->encolar(evento);
    }
    thread_send.join(); // join del hilo de enviar:	
    nuevo_socket->cerrar(); // Cerrar socket del cliente.	
}

void Server_thread_cliente::crearPartidaEnCampania(string& buffer) {
    //'nombreJugador|nombrePartida|indiceCampania"
    buffer.erase(0, 2); //saco C|
    unsigned pos = buffer.find("|"); //posicion del pipe.
    nombreJugador = buffer.substr(0, pos);
    string aux = buffer.substr(pos + 1);
    pos = aux.find("|");
    nombrePartida = aux.substr(0, pos);
    int indiceCampania = stoi(aux.substr(pos + 1));
    //creo jugador:
    this->jugador = new Jugador(this->nombreJugador);
    //creo partida:
    this->partida = new Partida(this->jugador, this->rutasCampanias[indiceCampania]);
    this->partida->setNombre(nombrePartida);
    this->mutex->lock();
    this->listaPartidas->push_back(this->partida);
    this->listaPartidasPendientes->push_back(this->partida);
    unordered_map<string, string> codigosJugador = this->partida->elegirSerialParaHost();
    this->jugador->obtenerPersonaje()->setSerial(codigosJugador);
    this->mutex->unlock();
    this->creadorPartida = true;
    this->agregadoAPartida = true;
}

void Server_thread_cliente::setNombreJugadorYAgregarAPartida(string& buffer) {
    buffer.erase(0, 2); //saco U|
    //'IdPartida|nombreJugador"
    unsigned pos = buffer.find("|"); //posicion del pipe.
    int idPartida = stoi(buffer.substr(0, pos));
    this->nombreJugador = buffer.substr(pos + 1);
    this->jugador = new Jugador(this->nombreJugador);
    this->mutex->lock();
    (*listaPartidas)[idPartida]->agregarJugador(this->jugador);
    this->partida = (*listaPartidas)[idPartida];
    unordered_map<string, string> codigosJugador = this->partida->elegirSerialParaJugador();
    this->jugador->obtenerPersonaje()->setSerial(codigosJugador);
    this->mutex->unlock();
    this->agregadoAPartida = true;
}

bool Server_thread_cliente::leerBuffer(string& buffer) {
    // Leer largo de lo recibido (largo establecido en 5 caracteres tipo 00500).
    string largoALeer;
    int result;
    if ((result = this->socket->recibir(nuevo_socket->get_fd(), &largoALeer, LARGO)) == -1) {
        cout << "server_thread_cliente" << endl;
    }
    if (result == 0) {
        return false;
    }
    int largo = stoi(largoALeer);
    //Leer la cantidad de bytes que dice el largo.
    if (this->socket->recibir(nuevo_socket->get_fd(), &buffer, largo) == -1) {
        cout << "server_thread_cliente recibir" << endl;
    }
    return true;
}

string Server_thread_cliente::agregarLargo(string buffer) {
    int largo = buffer.size() + 1;
    char* formato = (char*) malloc((LARGO + 1) * sizeof (char));
    snprintf(formato, LARGO + 1, "%.5d", largo);
    string aux;
    aux.append(string(formato));
    aux.append("|");
    aux.append(buffer);
    free(formato);
    return aux;
}

string Server_thread_cliente::generarInfoPartidas() {
    //"largo|nombrePartida|cantJugadores|cantRequerida|nombreCampaña|idem.."
    string buffer;
    string pipe = "|";
    this->mutex->lock();
    for (unsigned int i = 0; i < listaPartidas->size(); i++) {
        if (!(*listaPartidas)[i]->fueCancelada()) {
            buffer.append(pipe);
            buffer.append((*listaPartidas)[i]->getNombre());
            buffer.append(pipe);
            buffer.append(to_string((*listaPartidas)[i]->getCantJugadoresActual()));
            buffer.append(pipe);
            buffer.append(to_string((*listaPartidas)[i]->getCantJugadoresRequerida()));
            buffer.append(pipe);
            buffer.append((*listaPartidas)[i]->obtenerCampania().getNombre());
        }
    }
    this->mutex->unlock();
    return buffer;
}

string Server_thread_cliente::cantJugadoresEnPartida() {
    string buffer = to_string(this->partida->getCantJugadoresActual());
    return buffer;
}

void Server_thread_cliente::setFlagCorrer(bool* correr) {
    correServer = correr;
}

void Server_thread_cliente::setListaPartidas(vector<Partida*> *lista, vector<Partida*> * pendientes) {
    listaPartidas = lista;
    listaPartidasPendientes = pendientes;
}

void Server_thread_cliente::setInfoCampanias(string info) {
    infoCampanias = info;
}

void Server_thread_cliente::setNumeroCliente(int numero) {
    numeroCliente = numero;
}

void Server_thread_cliente::setTiempoActualizacion(int tiempo) {
    tiempoActualizacion = tiempo;
}

void Server_thread_cliente::setMutexLista(Mutex* mutex) {
    this->mutex = mutex;
}

void Server_thread_cliente::setRutasCampanias(vector<string> rutas) {
    this->rutasCampanias = rutas;
}

void Server_thread_cliente::setNombre(string nombre) {
    this->nombrePartida = nombre;
}